#!/usr/bin/python3
# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t -*-

import os
import glob
import logging
from bnet_util import Util
from bnet_param import WrtConst


class WrtSubnetl3Manager:

    def __init__(self, param):
        self._param = param
        self.logger = logging.getLogger(self.__module__ + "." + self.__class__.__name__)

        self.lifPluginList = []
        self.vpnsPluginList = []

        self.propDict = dict()              # dict<property-source,property-dict>

        self.clientDict = dict()            # dict<ip,data>
        self.clientSourceDict = dict()      # dict<ip,source_id>
        self.clientPropDict = dict()        # dict<ip,dict<property-source,property-dict>>

        try:
            # start all lan interface plugins
            for name in self._param.pluginHub.getPluginList("lif"):
                for instanceName, cfgObj, tmpdir, vardir in self._getInstanceAndInfoFromEtcDir("lif", "lan-interface", name):
                    os.mkdir(tmpdir)
                    Util.ensureDir(vardir)

                    p = self._param.pluginHub.getPlugin("lif", name, instanceName)
                    p.init2(instanceName, cfgObj, tmpdir, vardir)
                    p.start()
                    self.lifPluginList.append(p)
                    self.logger.info("LAN interface plugin \"%s\" activated." % (p.full_name))

            # start all vpn server plugins
            for name in self._param.pluginHub.getPluginList("vpns"):
                for instanceName, cfgObj, tmpdir, vardir in self._getInstanceAndInfoFromEtcDir("vpns", "vpn-server", name):
                    os.mkdir(tmpdir)
                    Util.ensureDir(vardir)

                    p = self._param.pluginHub.getPlugin("vpns", name, instanceName)
                    p.init2(instanceName,
                            cfgObj,
                            tmpdir,
                            vardir,
                            self._param.prefixPool.usePrefix(),
                            self._param.trafficManager.get_l2_nameserver_port(),
                            lambda source_id, ip_data_dict: self._clientAdd(source_id, ip_data_dict),
                            lambda source_id, ip_data_dict: self._clientChange(source_id, ip_data_dict),
                            lambda source_id, ip_list: self._clientRemove(source_id, ip_list))
                    p.start()
                    self.vpnsPluginList.append(p)
                    self.logger.info("VPN server plugin \"%s\" activated." % (p.full_name))

                    if p.get_wan_service() is not None:
                        self._param.trafficManager.add_wan_service(p.full_name, p.get_wan_service())

            # send other-bridge-create event
            all_bridges = [self.defaultBridge] + [x.get_bridge() for x in self.vpnsPluginList]
            for bridge in all_bridges:
                for other_bridge in all_bridges:
                    if bridge == other_bridge:
                        continue
                    bridge.add_source(other_bridge.get_bridge_id())
        except BaseException:
            self._dispose()
            raise

    def dispose(self):
        self._dispose()
        self.logger.info("Terminated.")

    def has_property(self, property_source):
        return property_source in self.propDict

    def set_property(self, property_source, property_dict):
        self.propDict[property_source] = property_dict

    def remove_property(self, property_source):
        del self.propDict[property_source]

    def set_client_property(self, ip, property_source, property_dict):
        if ip not in self.clientPropDict:
            self.clientPropDict[ip] = dict()
        self.clientPropDict[ip][property_source] = property_dict

        if ip in self.clientDict:
            data = self._clientDataFromIp(ip)
            self._param.managerCaller.call("on_client_change", self.clientSourceDict[ip], data)

    def remove_client_property(self, ip, property_source):
        del self.clientPropDict[ip][property_source]
        if len(self.clientPropDict[ip]) == 0:
            del self.clientPropDict[ip]

        if ip in self.clientDict:
            data = self._clientDataFromIp(ip)
            self._param.managerCaller.call("on_client_change", self.clientSourceDict[ip], data)

    def _clientAdd(self, source_id, ip_data_dict):
        assert len(ip_data_dict) > 0

        self.clientDict.update(ip_data_dict)
        for ip in ip_data_dict:
            self.clientSourceDict[ip] = source_id

        for bridge in [self.defaultBridge] + [x.get_bridge() for x in self.vpnsPluginList]:
            if source_id != bridge.get_bridge_id():
                bridge.add_host(source_id, ip_data_dict)

        data = self._clientDataFromIpDataDict(ip_data_dict)
        self._param.managerCaller.call("on_client_add", source_id, data)

    def _clientChange(self, source_id, ip_data_dict):
        assert len(ip_data_dict) > 0

        self.clientDict.update(ip_data_dict)

        for bridge in [self.defaultBridge] + [x.get_bridge() for x in self.vpnsPluginList]:
            if source_id != bridge.get_bridge_id():
                bridge.change_host(source_id, ip_data_dict)

        data = self._clientDataFromIpDataDict(ip_data_dict)
        self._param.managerCaller.call("on_client_change", source_id, data)

    def _clientRemove(self, source_id, ip_list):
        assert len(ip_list) > 0

        for ip in ip_list:
            del self.clientDict[ip]
            del self.clientSourceDict[ip]

        for bridge in [self.defaultBridge] + [x.get_bridge() for x in self.vpnsPluginList]:
            if source_id != bridge.get_bridge_id():
                bridge.remove_host(source_id, ip_list)

        self._param.managerCaller.call("on_client_remove", source_id, ip_list)

    def _clientDataFromIpDataDict(self, ip_data_dict):
        ret = dict()
        for ip in ip_data_dict:
            if ip in self.clientPropDict:
                ret[ip] = self.clientDict[ip].copy()
                for property_dict in self.clientPropDict[ip].values():
                    ret[ip].update(property_dict)
            else:
                ret[ip] = self.clientDict[ip]
        return ret

    def _clientDataFromIp(self, ip):
        ret = dict()
        if ip in self.clientPropDict:
            ret[ip] = self.clientDict[ip].copy()
            for property_dict in self.clientPropDict[ip].values():
                ret[ip].update(property_dict)
        else:
            ret[ip] = self.clientDict[ip]
        return ret

    def _getInstanceAndInfoFromEtcDir(self, pluginPrefix, cfgfilePrefix, name):
        # Returns (instanceName, cfgobj, tmpdir, vardir)

        ret = []
        for fn in glob.glob(os.path.join(WrtConst.etcDir, "%s-%s*.json" % (cfgfilePrefix, name))):
            bn = os.path.basename(fn)

            instanceName = bn[len("%s-%s" % (cfgfilePrefix, name)):len(".json") * -1]
            if instanceName != "":
                instanceName = instanceName.lstrip("-")

            if instanceName != "":
                fullName = "%s-%s" % (name, instanceName)
            else:
                fullName = name

            if os.path.getsize(fn) > 0:
                cfgObj = Util.loadJsonEtcCfg(fn)
            else:
                cfgObj = dict()

            tmpdir = os.path.join(self._param.tmpDir, "%s-%s" % (pluginPrefix, fullName))
            vardir = os.path.join(self._param.varDir, "%s-%s" % (pluginPrefix, fullName))

            ret.append((instanceName, cfgObj, tmpdir, vardir))

        if len(ret) == 0:
            instanceName = ""
            fullName = name
            cfgObj = dict()
            tmpdir = os.path.join(self._param.tmpDir, "%s-%s" % (pluginPrefix, fullName))
            vardir = os.path.join(self._param.varDir, "%s-%s" % (pluginPrefix, fullName))
            ret.append((instanceName, cfgObj, tmpdir, vardir))

        return ret

    def _dispose(self):
        for p in self.vpnsPluginList:
            p.stop()
            self.logger.info("VPN server plugin \"%s\" deactivated." % (p.full_name))
        self.vpnsPluginList = []

        for p in self.lifPluginList:
            p.stop()
            self.logger.info("LAN interface plugin \"%s\" deactivated." % (p.full_name))
        self.lifPluginList = []

        if self.defaultBridge is not None:
            self.defaultBridge.dispose()
            self.defaultBridge = None
            self.logger.info("Default bridge destroyed.")
